﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;
using MSNGame1;
using System.Windows.Browser;
using System.IO;

namespace Subattle
{
    public partial class Page : UserControl
    {
        const byte STARTGAME = 1;
        const byte UPDATE = 2;

        /// <summary>
        /// Is true if the remote client's MSN application loaded succesfully
        /// </summary>
        bool remoteClientLoaded = false;
        
        /// <summary>
        /// Is true if the client which is not the Host, has clicked ready
        /// </summary>
        bool clientReady = false;

        /// <summary>
        /// MSN Activity interface
        /// </summary>
        MSNActivity msn;

        /// <summary>
        /// A queue containing the torpedos we fired during the PacketSendInterval
        /// </summary>
        Queue<Torpedo> firedTorpedos = new Queue<Torpedo>();
        
        /// <summary>
        /// A queue of torpedos that hit the enemy during the PacketSendInterval
        /// </summary>
        Queue<Torpedo> netTorpedoImpacts = new Queue<Torpedo>();
        
        /// <summary>
        /// Our subs last set target, if we didn't change this and didn't fire any torpedos, the remoteclient doesnt need to know anything
        /// </summary>
        Vector2 currentTarget = Vector2.Zero;

        /// <summary>
        /// A timer that decides when we can send the remote client a new update
        /// </summary>
        double packetSendTimer = 0;

        /// <summary>
        /// The interval we wait between each packet 
        /// </summary>
        double packetSendInterval;




        void InitializeNetworking()
        {
            msn = new MSNActivity();
            msn.Channel.DataReceived += new EventHandler<Channel.DataArgs>(channel_ReceiveData);
            msn.Channel.ConnectionChanged += new EventHandler(channel_ConnectionChanged);
            msn.Channel.RemoteAppClosed += new EventHandler(channel_RemoteAppClosed);
            msn.Channel.RemoteAppLoaded += new EventHandler(Channel_RemoteAppLoaded);
            packetSendInterval = .05f;
        }
        
        void Channel_RemoteAppLoaded(object sender, EventArgs e)
        {
            // the remote client has loaded! 
            remoteClientLoaded = true;

            // If I'm the host
            if (msn.IsHost)
            {
                // find the other guy
                foreach (var user in msn.Users)
                {
                    if (user == msn.Users.Me)
                        continue;

                    PulseSize.Stop();

                    // And write that we're waiting for him
                    StatusText.Text = string.Format(strWaitingFor, user.Name);
                    soundPing.Play();
                }
            }
            else
            {
                PulseSize.Begin();
                // If im the client, I want to know that I can click now to mark myself as ready
                StatusText.Text = string.Format(strClickWhenReady);
            }
        }
        void channel_RemoteAppClosed(object sender, EventArgs e)
        {
            // If he closed his activity, we close ours
            msn.CloseActivity();
        }
        void channel_ConnectionChanged(object sender, EventArgs e)
        {
            // The amount of packets we're allowed to send is 2 each second when using an indirect connection, and all the packets we want when direct
            packetSendInterval = msn.Channel.Connection == Channel.ConnectionType.Direct ? 0.05f : 0.5f;

            // If however we were disconnected
            if (msn.Channel.Connection == Channel.ConnectionType.Disconnected)
            {
                // just close the activity
                msn.CloseActivity();
                
                // and cry :~(
                HtmlPage.Window.Alert(strDisconnected);
            }
        }
        void channel_ReceiveData(object sender, Channel.DataArgs e)
        {
            // receiving a packet from the other player!

            // convert the string to binary data.
            byte[] bytes = Convert.FromBase64String(e.Data);

            // drop it in a stream and put a binaryreader around it, to make it easier to read.
            MemoryStream ms = new MemoryStream(bytes);
            using (BinaryReader br = new BinaryReader(ms))
            {
                // first we read the packet type, which is a byte UPDATE or STARTGAME
                switch (br.ReadByte())
                {
                    // Init flag
                    case STARTGAME:
                        // if we're not the host
                        if (!msn.IsHost)
                        {
                            // then this packet contains all the info about our and their sub's position and target.
                            Enemy.Position = br.ReadVector2();
                            Enemy.Target = br.ReadVector2();

                            Sub.Position = br.ReadVector2();
                            Sub.Target = br.ReadVector2();
                            
                            // Update our game to reflect that we've begun!
                            BeginNewGame();
                        }
                        else
                        {
                            // if we are the host
                            // then this packet means the other player is ready!
                            PulseSize.Begin();
                            StatusText.Text = strStart;
                            soundResponse.Play();
                            clientReady = true;
                        }
                        break;
                    // Update flag
                    case UPDATE:
                        // the update packet contains the enemy's movements!
                        Enemy.Position = br.ReadVector2();
                        Enemy.Target = br.ReadVector2();
                        Enemy.Direction = br.ReadVector2();

                        // the torpedos he has fired
                        int newTorpedos = br.ReadInt32();

                        for (int i = 0; i < newTorpedos; i++)
                        {
                            // add those torpedos to our game
                            Torpedo t = torpedoHandler.SpawnTorpedo(Enemy, br.ReadVector2(), br.ReadVector2(),br.ReadByte());
                            
                            // but dont show them until we ping them :D 
                            t.Opacity = 0;
                        }

                        // the amount of times he hit us (damn him!) 
                        int hits = br.ReadInt32();
                        for (int i = 0; i < hits; i++)
                        {
                            // show that we were hit 
                            MySubHit(br.ReadVector2());
                            
                            // find the torpedo that hit us and remove it using an IDentifier
                            byte id = br.ReadByte();
                            for (int j = 0; j < torpedoHandler.Torpedos.Count; j++)
                            {
                                if (torpedoHandler.Torpedos[j].ID == id)
                                {
                                    LayoutRoot.Children.Remove(torpedoHandler.Torpedos[j]);
                                    torpedoHandler.Torpedos.RemoveAt(j--);
                                    break;
                                }
                            }
                        }
                        break;
                    default:
                        break;
                }
            }
        }
        void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            // when our silverlight application has loaded, we contact the remote client and tell them that!
            msn.Channel.Initialize();
        }

        /// <summary>
        /// Decides whether or not to send a packet to the other guys
        /// </summary>
        /// <param name="elapsed">time elapsed since last we checked</param>
        void NetUpdate(TimeSpan elapsed)
        {
            packetSendTimer -= (float)elapsed.TotalSeconds;

            // We send a network update if:
            //   we're allowed to by the packet limit imposed by MSN
            //   and we are not disconnected
            if (packetSendTimer <= 0 &&                           
                msn.Channel.Connection != Channel.ConnectionType.Disconnected)
            {
                packetSendTimer = packetSendInterval;
                SendUpdate();
            }
        }

        /// <summary>
        /// Sends an update to the remote player
        /// </summary>
        void SendUpdate()
        {
            // We write our packet using a BinaryWriter, because it's easier
            MemoryStream ms = new MemoryStream();
            using (BinaryWriter bw = new BinaryWriter(ms))
            {
                // This is an update packet!
                bw.Write(UPDATE);

                // send our position, target and direction so they can draw us correctly
                bw.Write(Sub.Position);
                bw.Write(Sub.Target);
                bw.Write(Sub.Direction);

                // How many torpedos did we fire since last update packet
                bw.Write(firedTorpedos.Count);
                while (firedTorpedos.Count > 0)
                {
                    // Put their position, velocity and id in the packet
                    Torpedo t = firedTorpedos.Dequeue();
                    bw.Write(t.Position);
                    bw.Write(t.Velocity);
                    bw.Write(t.ID);
                }

                // send how many times we hit them!
                bw.Write(netTorpedoImpacts.Count);
                while (netTorpedoImpacts.Count > 0)
                {
                    // and where we hit them along with the id of the torpedo that hit them
                    Torpedo t = netTorpedoImpacts.Dequeue();
                    bw.Write(t.Position);
                    bw.Write(t.ID);
                }

                // send the bytes to 555-REMOTE-CLIENT 
                msn.Channel.SendData(ms.ToArray());

                // record that we sent our target.
                currentTarget = Sub.Target;
            }
        }
        void SendStartGame()
        {
            // If we are the host 
            if (msn.IsHost)
            {
                // we wont do anything until the remote player is ready
                if (!clientReady)
                    return;

                // if he is ready we calculate the positions and targets of ours and their sub
                MemoryStream ms = new MemoryStream();
                using (BinaryWriter bw = new BinaryWriter(ms))
                {
                    // this is a STARTGAME packet, treat it as such ^^
                    bw.Write(STARTGAME);

                    // some random positions
                    Sub.Position = GenerateRandomPosition();
                    Enemy.Position = GenerateRandomPosition();

                    // some random targets
                    Sub.Target = GenerateRandomPosition();
                    Enemy.Target = GenerateRandomPosition();

                    // we just pack those into a packet (order matters! They need to read this stuff.)
                    bw.Write(Sub.Position);
                    bw.Write(Sub.Target);
                    bw.Write(Enemy.Position);
                    bw.Write(Enemy.Target);

                    // And away it goes
                    msn.Channel.SendData(ms.ToArray());
                }
                BeginNewGame();
            }
            else
            {
                // Were not the host

                // did we already say we were ready, then do nothing
                if (clientReady)
                    return;

                // otherwise we tell them we're ready
                MemoryStream ms = new MemoryStream();
                using (BinaryWriter bw = new BinaryWriter(ms))
                {
                    // this is a STARTGAME packet from the client to the host
                    bw.Write(STARTGAME);

                    // and thats all they need to know! 
                    msn.Channel.SendData(ms.ToArray());
                }
                
                PulseSize.Stop();
                StatusText.Text = string.Format(strWaitForHostToStart, msn.Users.Inviter.Name);
                soundPing.Play();
                
                // we are now ready!
                clientReady = true;
            }
        }
    }
    
    public static class Extensions
    {
        public static void Write(this BinaryWriter bw, Vector2 vector)
        {
            bw.Write(vector.X);
            bw.Write(vector.Y);
        }

        public static Vector2 ReadVector2(this BinaryReader bw)
        {
            return new Vector2(bw.ReadSingle(), bw.ReadSingle());
        }
    }
}
